﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

using Windows.UI.Xaml;

namespace Uhimania.Libraries.Windows.Interactivity
{
    #region AttachableCollection
    /// <summary>
    /// 共有される <see cref="AssociatedObject"/> とともに <see cref="IAttachedObject"/>
    /// のコレクションを表し、<see cref="AssociatedObject"/> に変更が発生すると
    /// 変更通知をコンテンツに提供する。
    /// </summary>
    /// <typeparam name="T">コレクション内の項目の型。</typeparam>
    public abstract class AttachableCollection<T> : ObservableCollection<T>, IAttachedObject
        where T : DependencyObject, IAttachedObject
    {
        #region OnCollectionChanged

        /// <summary>
        /// コレクションがクリアされたときに要素から <see cref="AssociatedObject"/>
        /// を適切にデタッチするための要素のコピー。
        /// </summary>
        private HashSet<T> snapshot = new HashSet<T>();

        /// <summary>
        /// 指定された引数を使用して <see cref="System.Collections.ObjectModel.ObservableCollection{T}.CollectionChanged"/>
        /// イベントを発生させる。
        /// </summary>
        /// <param name="e">発生させるイベント引数。</param>
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnCollectionChanged(e);

            var add = new Action<IEnumerable>(items =>
            {
                foreach (T item in items)
                {
                    this.ItemAdded(item);
                    this.snapshot.Add(item);
                }
            });
            var remove = new Action<IEnumerable>(items =>
            {
                foreach (T item in items)
                {
                    this.ItemRemoved(item);
                    this.snapshot.Remove(item);
                }
            });

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    add(e.NewItems);
                    break;

                case NotifyCollectionChangedAction.Remove:
                    remove(e.OldItems);
                    break;

                case NotifyCollectionChangedAction.Replace:
                    remove(e.OldItems);
                    add(e.NewItems);
                    break;

                case NotifyCollectionChangedAction.Move:
                    remove(e.OldItems);
                    add(e.NewItems);
                    break;

                case NotifyCollectionChangedAction.Reset:
                    foreach (T item in this.snapshot)
                        this.ItemRemoved(item);

                    this.snapshot.Clear();
                    add(this.Items);
                    break;
            }
        }

        #endregion

        #region AssociatedObject
        /// <summary>
        /// コレクションがホストされるオブジェクトを取得する。
        /// </summary>
        public DependencyObject AssociatedObject { get; set; }
        #endregion

        #region Attach

        /// <summary>
        /// 指定されたオブジェクトにアタッチする。
        /// </summary>
        /// <param name="associatedObject">アタッチするオブジェクト。</param>
        public void Attach(DependencyObject associatedObject)
        {
            if (this.AssociatedObject != associatedObject)
            {
                this.AssociatedObject = associatedObject;
                this.OnAttached();
            }
        }

        /// <summary>
        /// コレクションが <see cref="AssociatedObject"/> にアタッチされた直後に呼び出される。
        /// </summary>
        protected abstract void OnAttached();

        #endregion

        #region Detach

        /// <summary>
        /// このインスタンスを関連オブジェクトからデタッチする。
        /// </summary>
        public void Detach()
        {
            this.OnDetaching();
            this.AssociatedObject = null;
        }

        /// <summary>
        /// コレクションが <see cref="AssociatedObject"/> からデタッチされるとき、その前に呼び出される。
        /// </summary>
        protected abstract void OnDetaching();

        #endregion

        #region ItemAdded
        /// <summary>
        /// コレクションにアイテムが追加されたときに呼び出される。
        /// </summary>
        /// <param name="item">追加されたアイテム。</param>
        protected abstract void ItemAdded(T item);
        #endregion

        #region ItemRemoved
        /// <summary>
        /// コレクションからアイテムが削除されたときに呼び出される。
        /// </summary>
        /// <param name="item">削除されたアイテム。</param>
        protected abstract void ItemRemoved(T item);
        #endregion
    }
    #endregion
}
